        title "Initialize Stall Execution for the Corollary MP machines"
;++
;
;Copyright (c) 1992, 1993, 1994  Corollary Inc
;
;Module Name:
;
;    cb1stall.asm
;
;Abstract:
;
;   This module contains various stall initialization, clock and performance
;   counter routines.
;
;Author:
;
;   Landy Wang (landy@corollary.com) 05-Oct-1992
;
;Revision History:
;
;--



.386p
        .xlist
include halx86.inc
include x86\kimacro.inc
include callconv.inc                    ; calling convention macros
include cbus.inc
include macx86.inc
include x86\ix8259.inc

        EXTRNP  _HalBeginSystemInterrupt,3
        EXTRNP  _HalEndSystemInterrupt,2

        .list

;
; the CLKIN pin of the 82489DX is clocking at 32MHz (32000000) on the bridge,
; and 33-1/3 (333333333) MHz on the additional processor cards.
;
; divide this by 1,000,000 to get clocking in a microsecond --> ~33.
; 33 * 16 microseconds == 528 == 0x210
;

CLKIN_ONE_SECOND        EQU     32000000        ; in APIC CLKIN units
; CLKIN_ONE_SECOND        EQU     33333333        ; in APIC CLKIN units
CLKIN_SIXTEEN_MS        EQU     210h            ; 16 microseconds (CLKIN)
CLKIN_SIXTEEN_MS_SHIFT  EQU     4               ; shift 16 microseconds to 1ms
CLKIN_ENABLE_ONESHOT    EQU     0h      ; enable one-shot CLKIN interrupt
CLKIN_ENABLE_PERIODIC   EQU     20000h  ; enable periodic CLKIN interrupts
CLKIN_DISABLE           EQU     10000h  ; mask off CLKIN interrupts

APIC_TIMER_MILLISECOND  EQU     32000   ; timer units to == 1 millisecond
APIC_TIMER_MICROSECOND  EQU     33      ; timer units to == 1 microsecond

TIMER_VECTOR_ENTRY      EQU     320h    ; timer vector table entry 0 address
INITIAL_COUNT_REG       EQU     380h    ; poke here to set the initial count
CURRENT_COUNT_REG       EQU     390h    ; current counter countdown is here

D_INT032                EQU     8E00h   ; access word for x86 ring 0 int gate

CBUS1_PERF_TASKPRI      EQU     0DDh    ; vector generated by 8254 timer

;
; The default interval between clock interrupts is
; 10 milliseconds == 10000 microseconds == 100000 (in 100 ns units).
; Remember the NT executive expects this value to be in
; 100 nanosecond units (not microseconds), so multiply
; accordingly when referencing KeTimeIncrement.
;
; the maxclock rates below are not the slowest that the hardware can support -
; they are the slowest we want NT to restore it whenever it is rolled back.
;
MAXCLOCK_RATE_IN_MS     EQU     10     ; specify in milliseconds

;
; We will support clock interrupt generation with a minimum of 300
; nanosecond separations to as much as 10 milliseconds apart.
;
MINCLOCK_DELTA_IN_100NS  EQU     3            ; specify in 100-nanosecond units
MAXCLOCK_DELTA_IN_100NS  EQU     100000       ; specify in 100-nanosecond units

;
; "Convert" the interval to rollover count for 8254 Timer1 device.
; timer1 counts down a 16 bit value at a rate of 1.193M counts-per-sec.
; So that's what we'll use to get the finest granularity.  Note that
; this is solely for the performance counter and has NOTHING to do
; with the system clock, which is driven directly from the APIC.
; We'd like for the interrupt rate to be even lower (ie: once per
; second rather than approximately 17 times per second, but the 8254
; only gives us two bytes to feed in the interrupt counter.
;

ROLLOVER_COUNT          EQU     0ffffH  ; must fit in the 8254's two bytes
PERFORMANCE_FREQUENCY   EQU     1193000

TIMER1_DATA_PORT0       EQU     40H     ; Timer1, channel 0 data port
TIMER1_CONTROL_PORT0    EQU     43H     ; Timer1, channel 0 control port

COMMAND_8254_COUNTER0   EQU     00H     ; Select count 0
COMMAND_8254_RW_16BIT   EQU     30H     ; Read/Write LSB firt then MSB
COMMAND_8254_MODE2      EQU     4       ; Use mode 2
COMMAND_8254_BCD        EQU     0       ; Binary count down
COMMAND_8254_LATCH_READ EQU     0       ; Latch read command

_DATA   SEGMENT  DWORD PUBLIC 'DATA'
;
; default clock rate is 10 milliseconds
;
CbusClockRateInMillis          dd      10               ; in milliseconds

Cbus1PerfCounterInit    dd      0
Cbus1PerfCounterLow     dd      0
Cbus1PerfCounterHigh    dd      0
Cbus1LastReadPerfLow    dd      0
Cbus1LastReadPerfHigh   dd      0
Cbus18254Late           dd      0
Cbus1CurrentTimeIncrement dd     0

if DBG
Cbus18254LateCount      dd      0
Cbus1Queries            dd      0
endif

_DATA   ends


INIT    SEGMENT PARA PUBLIC 'CODE'     ; Start 32 bit code
        ASSUME  DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING

        page ,132
        subttl  "Initialize Stall Execution Counter"
;++
;
; VOID
; Cbus1InitializeStall (
;    IN CCHAR ProcessorNumber
;    )
;
; Routine Description:
;
;    This routine initializes the per Microsecond counter for
;    KeStallExecutionProcessor.
;
;    All Corollary processors have their own local APIC and
;    their own local timers.  each processor independently
;    calibrates himself here, thus supporting processors of
;    varying speeds.
;
; Arguments:
;
;    ProcessorNumber - Processor Number
;
; Return Value:
;
;    None.
;
;--

cPublicProc _Cbus1InitializeStall     ,1
cPublicFpo 1,2

        push    ebp                     ; save ebp
        mov     ebp, esp
        sub     esp, 8                  ; save room for idtr

        pushfd                          ; save caller's eflag

        cli                             ; disable interrupts

        ;
        ; save the current CbusClockVector IDT entry, as we are
        ; going to temporarily repoint it at a private handler.
        ;

        sidt    fword ptr [ebp-8]       ; get IDTR (base & limit)
        mov     ecx, [ebp-6]            ; get IDTR base value

        mov     eax, [_CbusClockVector]

        shl     eax, 3                  ; 8 bytes per IDT entry
        add     ecx, eax                ; now at the correct IDT RTC entry

        push    dword ptr [ecx]         ; (TOS) = original desc of IRQ 8
        push    dword ptr [ecx + 4]     ; each descriptor has 8 bytes
        push    ecx                     ; (TOS) -> &IDT[CbusClockVector]

        mov     eax, offset FLAT:TimerExpired

        mov     word ptr [ecx], ax              ; Lower half of handler addr
        mov     word ptr [ecx+2], KGDT_R0_CODE  ; set up selector
        mov     word ptr [ecx+4], D_INT032      ; x86 interrupt gate

        shr     eax, 16                 ; (ax)=higher half of handler addr
        mov     word ptr [ecx+6], ax

        mov     eax, [_CbusClockVector]      ; we expect this vector to...
        or      eax, CLKIN_ENABLE_ONESHOT  ; use one-shot CLKIN to interrupt us

        ; get the base of APIC space, so we can then access
        ; the addr of hardware interrupt command register below

        mov     ecx, [_CbusLocalApic]

        ; the count register appears to decrement approx 0x30 per
        ; asm instruction on a 486/33, just fyi.  so load it up with
        ; a "real high value" so we don't get an interrupt whilst setting
        ; up the local time vector entry, etc, and then at the last
        ; possible moment, fill it in with the desired starting value.

        mov     dword ptr INITIAL_COUNT_REG[ecx], CLKIN_ONE_SECOND

        ; initialize the local timer vector table entry with
        ; appropriate Vector, Timer Base, Timer Mode and Mask.

        mov     TIMER_VECTOR_ENTRY[ecx], eax

        ; poke initial count reg to interrupt us in 16 microseconds

        mov     dword ptr INITIAL_COUNT_REG[ecx], CLKIN_SIXTEEN_MS

        xor     eax, eax                ; initialize our register counter
ALIGN 16
        sti                             ; enable the interrupt
        jmp     kise10

ALIGN 16
kise10:
        sub     eax, 1                  ; increment the loopcount
        jnz     short kise10

if DBG
        stdCall   _DbgBreakPoint        ; Counter overflowed!
endif
        jmp     short kise10

TimerExpired:

        ; take the timer expiration interrupt here

if DBG
        cmp     eax, 0
        jnz     short kise30
        stdCall   _DbgBreakPoint        ; Counter was never bumped!
                                        ; never return
kise30:
endif

        neg     eax
        shr     eax, CLKIN_SIXTEEN_MS_SHIFT     ; convert to microseconds

        mov     dword ptr PCR[PcStallScaleFactor], eax

        mov     eax, [_CbusLocalApic]

        ; mask off local timer vector table entry now that we're done with it.

        mov     dword ptr TIMER_VECTOR_ENTRY[eax], CLKIN_DISABLE

        ;
        ; Dismiss the interrupt AFTER disabling the timer entry so
        ; we don't get an extra interrupt later that was already pending.
        ;

	mov     eax, _CbusClockVector   ; mark interrupting vector
        CBUS_EOI eax, ecx               ; destroy eax & ecx

        add     esp, 12                 ; unload flags, cs, ip

        pop     ecx                     ; (ecx) -> &IDT[CbusClockVector]
        pop     [ecx+4]                 ; restore higher half of RTC desc
        pop     [ecx]                   ; restore lower half of RTC desc

        popfd                           ; restore caller's eflags
        mov     esp, ebp
        pop     ebp                     ; restore ebp

        stdRET    _Cbus1InitializeStall

stdENDP _Cbus1InitializeStall

        page ,132
        subttl  "Cbus1 Initialize Performance Counter"
;++
;
; VOID
; Cbus1InitializePerf (
;    )
;
; Routine Description:
;
;    Initialize the 8254 to interrupt the minimum number of times
;    per second on the boot processor only to support the performance counter.
;
; Arguments:
;
;    None
;
; Return Value:
;
;    None.
;
;--
cPublicProc _Cbus1InitializePerf      ,0

        ;
        ; Since ke\x86\allproc.c no longer boots all the available
        ; processors in the machine, the first processor to boot must
        ; initialize the 8254 and take the extra 8254 ticks.
        ;

        mov     eax, PCR[PcHal.PcrNumber]
        cmp     eax, 0
        jne     short @f

        pushfd                          ; save caller's eflag
        cli                             ; make sure interrupts are disabled

        ;
        ; Initialize the APIC so that 8254 interrupts go only to the boot
        ; processor - there is no need for all of them to get this interrupt
        ; when all it is doing is updating a global counter.
        ;

        ; stdCall _HalEnableSystemInterrupt,<CBUS1_PERF_TASKPRI,CLOCK2_LEVEL,0>
        ; no need to enable - it's done by our caller

        ;
        ; Set clock rate at the 8254
        ;

        mov     al,COMMAND_8254_COUNTER0+COMMAND_8254_RW_16BIT+COMMAND_8254_MODE2
        out     TIMER1_CONTROL_PORT0, al ;program count mode of timer 0
        IoDelay
        mov     ecx, ROLLOVER_COUNT
        mov     al, cl
        out     TIMER1_DATA_PORT0, al   ; program timer 0 LSB count
        IoDelay
        mov     al,ch
        out     TIMER1_DATA_PORT0, al   ; program timer 0 MSB count

        popfd                           ; restore caller's eflag

        mov     Cbus1PerfCounterInit, 1

        align   4
@@:

        stdRET    _Cbus1InitializePerf

stdENDP _Cbus1InitializePerf

        INIT    ends

_TEXT   SEGMENT DWORD PUBLIC 'CODE'     ; Start 32 bit code
        ASSUME  DS:FLAT, ES:FLAT, SS:NOTHING, FS:NOTHING, GS:NOTHING

        page ,132
        subttl  "Cbus1 Initialize Clock"
;++
;
; VOID
; Cbus1InitializeClock (
;    )
;
; Routine Description:
;
;   Initializes the clock and the kernel variable for the amount of
;   time between timer ticks.  The kernel needs this information by the end
;   of phase 0.
;
; Arguments:
;
;    None
;
; Return Value:
;
;    None.
;
;--

cPublicProc _Cbus1InitializeClock,0

        ;
        ; Fill in value with the time between clock ticks,
        ; remember the NT executive expects this value to be in
        ; 100 nanosecond units, not microseconds, so multiply accordingly.
        ;

        mov     eax, CbusClockRateInMillis     ; current rate in milliseconds

        mov     ecx, 10000                      ; converting back to 100 ns
        mul     ecx                             ; eax == 100ns unit value

        ;
        ; The Cbus1CurrentTimeIncrement value is used by the clock
        ; interrupt routine to pass to the kernel, so set it now.
        ;
        mov     Cbus1CurrentTimeIncrement, eax

        stdCall _Cbus1ProgramClock

        stdCall _KeSetTimeIncrement, <MAXCLOCK_DELTA_IN_100NS, MINCLOCK_DELTA_IN_100NS>

        stdRET    _Cbus1InitializeClock

stdENDP _Cbus1InitializeClock

;++
;
; VOID
; Cbus1ProgramClock (
;    )
;
; Routine Description:
;
;    This routine initializes the system time clock for each calling
;    processor to generate periodic interrupts at the specified rate using
;    this processor's local APIC timer.  Thus, each processor must call
;    this routine to set or change his clock rate.  During startup, each
;    processor will call this routine to set his own clock rate.  Later,
;    when the system is running, the HalSetTimerResolution API is only
;    supposed to change the clock rate for the boot processor - this is
;    for multimedia apps that want timeouts serviced at a better than
;    5 millisecond granularity.
;
; Arguments:
;
;    None
;
; Return Value:
;
;    None.
;
;--

cPublicProc _Cbus1ProgramClock      ,0

        mov     ecx, CbusClockRateInMillis     ; new rate in milliseconds

        mov     eax, APIC_TIMER_MILLISECOND     ; counter per microsecond

        mul     ecx                             ; eax == new APIC countdown val

        mov     edx, [_CbusClockVector]         ; we expect this vector to...
        or      edx, CLKIN_ENABLE_PERIODIC      ; use CLKIN to interrupt us

        mov     ecx, [_CbusLocalApic]

        pushfd
        cli

        ; as a frame of reference, the count register decrements
        ; approximately 0x30 per asm instruction on a 486/33.

        ; initialize the local timer vector table entry with
        ; appropriate Vector, Timer Base, Timer Mode and Mask.

        mov     TIMER_VECTOR_ENTRY[ecx], edx

        ; poke initial count reg to start the periodic clock timer interrupts.
        ; the IDT entry is valid & enabled on entry to this routine.

        mov     dword ptr INITIAL_COUNT_REG[ecx], eax

        mov     eax, CbusClockRateInMillis     ; new rate in milliseconds
        mov     ecx, 10000                      ; converting back to 100 ns
        xor     edx, edx
        mul     ecx                             ; eax == 100ns unit value

        ;
        ; store it here so the clock ISR can tell it to the kernel
        ;
        mov     Cbus1CurrentTimeIncrement, eax

        popfd
        stdRET    _Cbus1ProgramClock

stdENDP _Cbus1ProgramClock

        page ,132
        subttl  "Query Performance Counter"
;++
;
; LARGE_INTEGER
; Cbus1QueryPerformanceCounter (
;    OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL
;    )
;
; Routine Description:
;
;    This routine returns the current 64-bit performance counter.
;    The Performance Frequency is also returned if asked for.
;
;    Also note that the performance counter returned by this routine
;    is not necessarily the value exactly when this routine was entered.
;    The value returned is actually the counter value at any point
;    between the routine's entrance and exit times.
;
;    This routine is not susceptible to the 2 problems that plague most
;    multiprocessor HALs.  Their problems are as follows:
;
;    a) If the boot processor (or whoever updates the global performance
;    counters) misses an interrupt, the counter rolls over undetected,
;    and the performance counter returned can actually roll backwards!
;
;    b) If you are on an additional processor and for some reason,
;    the boot processor is holding off a pending clock interrupt for
;    the entire time the additional processor is executing in
;    KeQueryPerfomanceCounter.  The boot processor hasn't necessarily
;    lost a clock interrupt, it's just that he hasn't dropped IRQL
;    enough to get it yet, and there isn't any good way for the
;    additional processor to force him to.  Since the boot processor
;    is the only one maintaining the PerfCounter[High,Low], this can
;    result in processors returning potentially non-uniform snapshots,
;    which can even roll backwards!
;
;    Both of these problems have been solved in the Corollary HAL by
;    using separate clocks for the system timer and the performance counter.
;    Ie: the APIC timer is used for the system timer and interrupts each
;    processor every 15 milliseconds.  The 8254 interrupts ONLY the boot
;    processor once every second.  Thus, case a) above cannot happen
;    unless the boot processor disables interrupts for more than one second.
;    If this happens, the entire system will be badly broken.  case b)
;    is also bypassed by using a global lock in the performance counter
;    interrupt handler.  Since the interrupt only occurs once per second
;    and only on one processor, the global lock will not hinder performance.
;    This allows us to take globally synchronized performance counter
;    snapshots and also detect in software if case b) is happening, and
;    handle it in software.  Because the 8254 timer register is only 16 bits
;    wide, the actual 8254 synchronization interrupt will occur approximately
;    seventeen times per second instead of once.  This is still at least 4
;    times better than the system timer (approximately 70 times per second).
;
; Arguments:
;
;    PerformanceFrequency [TOS+4] - optionally, supplies the address
;        of a variable to receive the performance counter frequency.
;
; Return Value:
;
;    Current value of the performance counter will be returned.
;
;--


;
; Parameter definitions
;

KqpcFrequency   EQU     [esp+4]         ; User supplied Performance Frequency

cPublicProc _Cbus1QueryPerformanceCounter      ,1

        ;
        ; First check to see if the performance counter has been
        ; initialized yet.  Since the kernel debugger calls
        ; KeQueryPerformanceCounter to support the !timer command
        ; _VERY_ early on, we need to return something reasonable
        ; even though timer initialization hasn't occured yet.
        ;
        cmp     Cbus1PerfCounterInit, 0
        jne     short @f                ; ok, perf counter has been initialized

        ;
        ; Initialization hasn't occured yet, so just return zeros.
        ;
        mov     eax, 0
        mov     edx, 0
        jmp     retfreq                 ; retfreq too far away for short jmp

        align   4
@@:
if DBG
        inc     dword ptr [Cbus1Queries]
endif
        push    ebx
        push    esi
        ;
        ; all interrupts must be disabled prior to lock acquisition to
        ; prevent deadlock.
        ;
        pushfd
        cli

        lea     esi, _Halp8254Lock

        align   4
Kqpc00:
        ACQUIRE_SPINLOCK esi, Kqpc01

        ;
        ; get the global timer counters which are incremented
        ; one processor only.
        ;

        mov     esi, Cbus1PerfCounterLow
        mov     ebx, Cbus1PerfCounterHigh ; [ebx:esi] = Performance counter

        ;
        ; Fetch the current counter value from the hardware
        ;

        mov     al, COMMAND_8254_LATCH_READ+COMMAND_8254_COUNTER0
                                        ;Latch PIT Ctr 0 command.
        out     TIMER1_CONTROL_PORT0, al
        IODelay
        in      al, TIMER1_DATA_PORT0   ;Read PIT Ctr 0, LSByte.
        IODelay
        movzx   ecx,al                  ;Zero upper bytes of (ECX).
        in      al, TIMER1_DATA_PORT0   ;Read PIT Ctr 0, MSByte.
        mov     ch, al                  ;(CX) = PIT Ctr 0 count.

        neg     ecx                     ; PIT counts down to 0h
        add     ecx, ROLLOVER_COUNT
        add     esi, ecx
        adc     ebx, 0                  ; [ebx:esi] = Final result

        ;
        ; Check whether case b) could be happening right now - this is
        ; possible no matter which processor is currently calling us.
        ;

        cmp     ebx, dword ptr [Cbus1LastReadPerfHigh]
        jl      short caseb

        cmp     esi, dword ptr [Cbus1LastReadPerfLow]
        jl      short caseb

        jmp     short notpending

        align   4
caseb:
        ;
        ; Detected case b) happening RIGHT NOW!
        ; Update the Cbus1 performance counter NOW, and
        ; set a flag so the interrupt handler will NOT.
        ; This case actually happens fairly frequently,
        ; ie: at least every few seconds on an idle
        ; uniprocessor, so this code is quite useful.
        ;

        add     Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter
        adc     Cbus1PerfCounterHigh, 0
        inc     dword ptr [Cbus18254Late]

if DBG
        inc     dword ptr [Cbus18254LateCount]
endif

        align   4
notpending:
        ;
        ; save last performance counter read so future callers can compare
        ;

        mov     Cbus1LastReadPerfLow, esi
        mov     Cbus1LastReadPerfHigh, ebx

        mov     edx, ebx                        ; save return value
        mov     eax, esi                        ; save return value

        lea     esi, _Halp8254Lock
        RELEASE_SPINLOCK    esi

        popfd
        pop     esi
        pop     ebx

        ;
        ; if asked to, return the frequency in units/second
        ;

        align   4
retfreq:

        or      dword ptr KqpcFrequency, 0              ; is it NULL?
        jz      short @f                                ; if z, yes, go exit

        mov     ecx, KqpcFrequency                      ; frequency pointer
        mov     dword ptr [ecx], PERFORMANCE_FREQUENCY  ; set frequency
        mov     dword ptr [ecx+4], 0                    ; currently < 4Gig!

        align   4
@@:
        stdRET    _Cbus1QueryPerformanceCounter

        align   4
Kqpc01:
        SPIN_ON_SPINLOCK   esi,<Kqpc00>

stdENDP _Cbus1QueryPerformanceCounter

        page ,132
        subttl  "Cbus1 Perf Interrupt"
;++
;
; VOID
; Cbus1PerfInterrupt(
;       VOID
;       );
;
; Routine Description:
;
;    This routine is the interrupt handler for the Cbus1 performance
;    counter interrupt at a priority just below that of normal clocks.
;    Its function is to update the global performance counter so that
;    KeQueryPerformanceCounter can return meaningful values.
;
;    This routine is executed only by one processor at a rate of seventeen
;    times per second, as there is no need for all processors to update
;    the same global counter.  The only reason the rate is so high is
;    because the interrupt interval must fit into a 16 bit register in the
;    8254.  Otherwise, it would have been more like once per second.
;
;    Since this routine is entered directly via an interrupt gate, interrupt
;    protection via cli is not necessary.
;
; Arguments:
;
;    None
;
; Return Value:
;
;    None.
;
;--

        ENTER_DR_ASSIST hipi_a, hipi_t

cPublicProc _Cbus1PerfInterrupt   ,0

        ;
        ; Save machine state on trap frame
        ;

        ENTER_INTERRUPT hipi_a, hipi_t

        ; keep it simple, just issue the EOI right now.
        ; no changing of taskpri/irql is needed here.
        ; Thus, the EOI serves as the HalEndSystemInterrupt.

	mov     eax, CBUS1_PERF_TASKPRI         ; mark interrupting vector
        CBUS_EOI eax, ecx                       ; destroy eax & ecx

        ;
        ; (esp) - base of trap frame
        ;

ifdef MCA

        ;
        ; Special hack for MCA machines
        ;

        in      al, 61h
        jmp     $+2
        or      al, 80h
        out     61h, al
        jmp     $+2

endif   ; MCA

        lea     esi, _Halp8254Lock

        align   4
Kcpi00:
        ACQUIRE_SPINLOCK esi, Kcpi01

        ;
        ; Update Cbus1 performance counter if a performance counter query
        ; hasn't done so already.
        ;

        cmp     dword ptr [Cbus18254Late], 0
        jne     short @f

        add     Cbus1PerfCounterLow, ROLLOVER_COUNT ; update performance counter
        adc     Cbus1PerfCounterHigh, 0
        jmp     short noroll

        align   4
@@:
        dec     dword ptr [Cbus18254Late]

        align   4
noroll:
        RELEASE_SPINLOCK    esi

        ;
        ; Call this directly instead of through INTERRUPT_EXIT
        ; because the HalEndSystemInterrupt has already been done,
        ; and must only be done ONCE per interrupt.
        ;

        SPURIOUS_INTERRUPT_EXIT     ; exit interrupt without eoi

        align   4
Kcpi01:
        SPIN_ON_SPINLOCK    esi,<Kcpi00>

stdENDP _Cbus1PerfInterrupt

;++
;
; ULONG
; Cbus1SetTimeIncrement (
;     IN ULONG DesiredIncrement
;     )
;
; /*++
;
; Routine Description:
;
;       This routine initializes the system time clock to generate an
;       interrupt at every DesiredIncrement interval.
;
; Arguments:
;
;       DesiredIncrement - desired interval between every timer tick in
;                        100ns units.
;
; Return Value:
;
;       The *REAL* time increment set - this can be different from what he
;       requested due to hardware limitations - currently to keep the math
;       simple, we limit the interval to between 1 and 32000 milliseconds,
;       on millisecond boundaries (ie 1.3 milliseconds is rounded to 1
;       millisecond).
;--

cPublicProc _Cbus1SetTimeIncrement,1

        mov     eax, [esp+4]                    ; caller's desired setting
        xor     edx, edx
        mov     ecx, 10000
        div     ecx                             ; round to milliseconds

        cmp     eax, MAXCLOCK_RATE_IN_MS        ; desired > max?
        jc      short @f
        mov     eax, MAXCLOCK_RATE_IN_MS        ; yes, use max
@@:

        or      eax, eax                        ; MS < min?
        jnz     short @f
        inc     eax                             ; yes, use min
@@:

        mov     CbusClockRateInMillis, eax     ; set new rate in milliseconds

	;
	; inform this processor's local APIC hardware of the change.
	; and then tell all the other processors to update theirs too...
	;
        stdCall _Cbus1ProgramClock

        mov     eax, Cbus1CurrentTimeIncrement

        stdRET  _Cbus1SetTimeIncrement

stdENDP _Cbus1SetTimeIncrement

        page ,132
        subttl  "System Clock Interrupt"
;++
;
; Routine Description:
;
;    This routine is entered as the result of an interrupt generated by CLOCK.
;    Its function is to dismiss the interrupt, raise system Irql to
;    CLOCK2_LEVEL, update performance counter and transfer control to the
;    standard system routine to update the system time and the execution
;    time of the current thread and process.
;
;    Note spurious interrupts are always sent to the spurious interrupt IDT
;    entry, and hence, no need to check for one here.
;
;    See also Cbus1ClockInterruptPx() - it's used by the non-boot processors,
;    since additional processors don't need to bump any global counters.
;
; Arguments:
;
;    None
;    Interrupt is disabled
;
; Return Value:
;
;    must set up eax with the increment value, also leave ebp pointing at
;    base of trap frame.
;    Does not return, jumps directly to KeUpdateSystemTime, which returns
;
;    Sets Irql = CLOCK2_LEVEL and dismisses the interrupt
;
;--
        ENTER_DR_ASSIST Hck_a, Hck_t

cPublicProc _Cbus1ClockInterrupt     ,0

	;
	; Save machine state in trap frame
	; (esp) - base of trap frame
	;

        ENTER_INTERRUPT Hck_a, Hck_t

	;
	; Dismiss interrupt and raise irq level to clock2 level
	;

        push    _CbusClockVector
        sub     esp, 4                  ; allocate space to save OldIrql
        stdCall   _HalBeginSystemInterrupt, <CLOCK2_LEVEL, _CbusClockVector, esp>

        POKE_LEDS eax, edx

	;
	; (esp)   = OldIrql
	; (esp+4) = Vector
	; (esp+8) = base of trap frame
        ; (ebp)   = address of trap frame
        ; (eax)   = time increment
	;
        mov     eax, Cbus1CurrentTimeIncrement

        jmp     _KeUpdateSystemTime@0

stdENDP _Cbus1ClockInterrupt
_TEXT   ends

        end
